/**
**************************************************************************************
*Palisma - Secrets of the Illuminati is an open-source 2D RPG                        *
*Copyright (C) 2006, Tony Sparks                                                     *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
#include "stdafx.h"
#include "IEntity.h"
#include "IWeapon.h"
#include "PhysicsObject.h"
#include "../render/ViewManager.h"
#include "../render/EmitterFactory.h"
#include "../render/ExplotionEmitter.h"
#include <math.h>
#include "../kernel.h"

extern Kernel* g_kernel;

#define TIME g_kernel->GetTime()
#define RUN_SCRIPT( str ) (g_kernel->GetScriptEngine()->RunFile( #str ))

IEntity::IEntity()
{
    m_active    = true; 
    m_remove    = false;
    m_readInput = true;
    m_name      = "Entity"; 
    
    m_bounds.Reset();
    m_actions       = 0;
    m_health        = 0;
    m_flags         = 0;
    m_id            = 0; 
    m_nextUpdate    = 0.0f;
    m_moveTime      = 0.0f;
    m_heightAboveGround = 0.0f;
    m_gravity       = 0.0f;
    m_face          = SOUTH; 
    m_oldFace       = SOUTH;
    m_fSpeed        = 0.0f;

    m_dest.ZeroOut();   
    m_throwVel.ZeroOut();
    m_forward.ZeroOut();
    m_direction.ZeroOut();
    m_physObject.SetMass( 1.0f );
    //m_world.ZeroOut();
    //m_velocity.ZeroOut();
    //m_baseVelocity.ZeroOut();
    //m_position.ZeroOut();

    m_carriedEntity     = NULL;
    m_carrier           = NULL;

    m_entCtrl.SetEntity( this );
    m_FSM.SetOwner( this );
}

IEntity::IEntity(const IEntity &ent)
{
    m_active        = ent.m_active; 
    m_remove        = ent.m_remove;;
    m_name          = ent.m_name; 
    
    m_bounds        = ent.m_bounds;
    m_health        = ent.m_health;
    m_flags         = ent.m_flags;
    m_face          = ent.m_face; 
    m_oldFace       = ent.m_oldFace;
    m_fSpeed        = ent.m_fSpeed;

    m_dest          .Set( ent.m_dest );   
    m_throwVel      .Set( ent.m_throwVel );
    m_forward       .Set ( ent.m_forward );
    m_direction     .Set( ent.m_direction );
    
    m_physObject.SetMass( 1.0f );

    m_actions       = 0;
    m_id            = 0;
    m_nextUpdate    = 0.0f;
    m_moveTime      = 0.0f;
    m_heightAboveGround = 0.0f;
    m_gravity       = 0.0f;
    m_carriedEntity = NULL;
    m_carrier       = NULL;

    // TODO - copy the dictionary

    m_entCtrl.SetEntity( this );
    m_FSM.SetOwner( this );
}

// TODO - delete states
IEntity:: ~IEntity()
{
    g_kernel->LPrintf( "***Entity Memory Report: %d  bytes", sizeof( *this ) );
    // Delete all the states
    type_States::iterator it = m_states.begin();
    for( ; it != m_states.end(); it++ )
    {
        if ( it->second )
            delete it->second;
    }
    // remove its models
    ViewManager::GetInstance()->RemoveView( GetID() );

    
}

/** Apply Common Dictionary Attributes */
void IEntity::Apply()
{
    m_physObject.SetPosition( m_attributes.GetVector2f("location") );
    m_fSpeed = m_attributes.GetInt("base_speed");

    m_health = m_attributes.GetInt("health");

    int width = m_attributes.GetInt("width");
    int height = m_attributes.GetInt("height");
    Vector2f boundingBoxOffset = m_physObject.GetPosition() + m_attributes.GetVector2f( "bboffset" );
    SetBounds(boundingBoxOffset.x, boundingBoxOffset.y, width, height);
}

///** Update the Position Coordinates */
//void IEntity::UpdateScreenCoord( const Vector2f &screenCoords )
//{
//    // update the screen coords
//    //m_position.Set( screenCoords );
//}

/** Update bounds */
void IEntity::UpdateCollisionBounds( int x, int y )
{
    Rect bounds = GetBounds();
    Vector2f boundingBoxOffset = Vector2f( x, y ) + m_attributes.GetVector2f( "bboffset" );
    SetBounds(boundingBoxOffset.x, boundingBoxOffset.y, bounds.width, bounds.height );
}


/** Update the entity */
void IEntity::Update( long dt) 
{ 
    m_FSM.Update();
    if (!m_weaponInventory.IsEmpty())
        m_weaponInventory.CurrentItem()->Update( dt );

    // we are in the possession of 
    // someone else or being thrown
    if ( GetState() == ENT_STATES[ THROWN ] || CarriedBy() )
        return;

    CheckFace(m_actions);   // check the direction of the entity

    // The entity is attempting to attack
    if ( m_actions & BUTTON_ATTACK1 )
    {
        if ( CarryingEntity() )
        {
            Throw();
        }
        else if ( !m_weaponInventory.IsEmpty() )
        {   
            IWeapon* weapon = m_weaponInventory.CurrentItem();
            if ( weapon )
            {
                weapon->Fire();
            }
        }
        return;
    }

    // The entity is attempting to 'use'
    if ( m_actions & BUTTON_USE )
    {
        if ( CarryingEntity() )
        {
            Throw();
        }
        else
        {
            // search for an entity in a region
            IEntity* ent = EntityManager::GetInstance()->FindInRegion( GetReachRectangle(), this );
            
            if ( ent )
            {  
                if ( ent->m_attributes.Has( "dialog" ) ) 
                {
                    ent->SetFace( (GetFace() + 2) % 4);
                    ent->SetState( ENT_STATES[TALKING] );
                }
                else if ( ent->m_attributes.Has( "liftable" ) ) 
                {
                    if ( !CarryingEntity() )
                    {
                        Carry( ent );
                    }
                }
                
            }
        }
        return;
    }

    // Check for movement
    if ( m_direction.Length() )
    {
        // switch the state to walking
        if ( CarryingEntity() )
        {
            SetState( ENT_STATES[CARRYING_WALKING] );
        }
        else
        {
            SetState( ENT_STATES[WALKING] );
        }
    } 
    else if ( GetState() != ENT_STATES[TALKING] )
    {
        // switch the state to idle
        if ( CarryingEntity() )
        {
            SetState( ENT_STATES[CARRYING] );
        }
        else
        {
            SetState( ENT_STATES[IDLE] );
        }
    }
}

/** Damage this entity */
int IEntity::Damage( int hit )
{
    // Make sure this entity is destructable
    if ( !m_attributes.Has("health") )
        return 0;

    SetState( ENT_STATES[HIT] );      // set to the hit state

    int old   = m_health;   // save old health
    m_health += hit;        // damage the health
    
    // if we are holding something, throw it
    if ( CarryingEntity() ) Throw();

    // set the dead state
    if ( m_health <= 0 ) 
    {
        SetState(ENT_STATES[DEAD]);
    }

    // TEMP~~~~
    if ( hit < 0 )
    {
        //ExplotionEmitter* blood = new ExplotionEmitter;//Factory::GetInstance()->CreateEmitter<ExplotionEmitter*>("");
        //blood->SpawnRate = 0;
        //blood->MaxParticles = 5;
        //blood->MaxTotalParticles = 5;
        //blood->LifeSpan = 800; 
        //blood->LifeSpanOfEmitter = TIME + 2000; // 4 sec
        //blood->StartColor = Vector3f( 0.596f, 0.0313f, 0.0313f );
        //blood->EndColor = Vector3f( 0.596f, 0.0313f, 0.0313f );
        //blood->ColorFadeTime = 0.0267f;
        //blood->StartAlpha = 1.0f;
        //blood->AlphaFadeTime = 60;
        //blood->ParticleSize = 3.0f;
        //blood->Acceleration = Vector2f( 44.05f, 44.05f );
        //blood->Position = m_world + Vector2f( 32, 32);
                ExplotionEmitter blood;// = new ExplotionEmitter;//Factory::GetInstance()->CreateEmitter<ExplotionEmitter*>("");
        blood.SpawnRate = 0;
        blood.MaxParticles = 5;
        blood.MaxTotalParticles = 5;
        blood.LifeSpan = 800; 
        blood.LifeSpanOfEmitter = TIME + 2000; // 4 sec
        blood.StartColor = Vector3f( 0.596f, 0.0313f, 0.0313f );
        blood.EndColor = Vector3f( 0.596f, 0.0313f, 0.0313f );
        blood.ColorFadeTime = 0.0267f;
        blood.StartAlpha = 1.0f;
        blood.AlphaFadeTime = 60;
        blood.ParticleSize = 3.0f;
        blood.Acceleration = Vector2f( 44.05f, 44.05f );
        blood.Position = m_physObject.GetPosition() + Vector2f( 32, 32);

        IEvent evt(EVT_ADD_PARTICLE, TIME, new ParticleData(blood), LT_PARTICLEMNG );
        EventManager::GetInstance()->TriggerEvent( evt );

        static int count = 0;
        //std::cout << "We have been hit! " << count++ << "\n";
    }
    return old;
}

/** Throws the entity */
void IEntity::Throw()
{
    if ( CarryingEntity() )
    {
        SetState( ENT_STATES[THROWING] );
        m_carriedEntity->SetState( ENT_STATES[THROWN] );

        Vector2f thrownTo = m_forward * 2.0f;
        m_carriedEntity->m_heightAboveGround = 16;
        m_carriedEntity->MoveToOverTime( thrownTo, TIME + 500 );

        m_carriedEntity->m_carrier = NULL; 
        m_carriedEntity = NULL; 
    }

}


/** Pick Up an Item */
void IEntity::PickUpItem(IEntity* ent)
{
    ent->m_flags &= ~EF_WP_ONGROUND; // turn off the on ground flag
    ent->m_flags |= EF_NOTONSCREEN;  // don't draw it either
    EntityManager::GetInstance()->RemoveWithoutDeleting( ent );

    // run a picked up script if needed
    if ( ent->m_attributes.Has( "pickedup" ) )
        RUN_SCRIPT( ent->m_attributes.GetStr( "pickedup" ) );

    switch( ent->GetType() )
    {
    case TYPE_BASE:
        m_itemInventory.Add( ent );
        break;
    case TYPE_WEAPON:
        IWeapon* weapon = static_cast<IWeapon*>(ent);
        weapon->SetOwner( this );

        m_weaponInventory.Add( weapon );
        break;
    }
}


/** Arrived to location */
bool IEntity::ArrivedToLocation() 
{ 
    return TIME > m_moveTime; 
} 


/** Get the Reach rectangle - creates a rect infront of entity */
Rect IEntity::GetReachRectangle()
{
    // distance to move 
    int w = GetBounds().width;
    int h = GetBounds().height;

    Vector2f spot( m_physObject.GetPosition() );   
    switch( GetFace() )
    {
    case NORTH:
        break;
    case EAST:  
        spot.y += h; 
        spot.x += w; 
        break;
    case SOUTH: 
        spot.y += (h*2);
        break;
    case WEST:  
        spot.y += h; 
        spot.x -= w;
        break;
    }
    return Rect(spot.x, spot.y, w, h);
}

/** Check the face Direction of the player */
void IEntity::CheckFace(int action)
{
    m_direction.ZeroOut();
    int lastFace = GetFace(); 

    // vertical movement
    if( action & BUTTON_UP ) {
        m_direction.y = -1;
        if( lastFace == SOUTH || !(action & (BUTTON_LEFT|BUTTON_RIGHT)) ) {
            if ( !(action & BUTTON_STRAFE) )
                SetFace(NORTH);
        }
    } else if ( action & BUTTON_DOWN ) {
        m_direction.y = 1;
        if( lastFace == NORTH  || !(action & (BUTTON_LEFT|BUTTON_RIGHT)) ) {
            if ( !(action & BUTTON_STRAFE) )
                SetFace(SOUTH);
        }
    }

    // horizontal movement
    if( action & BUTTON_LEFT ) {
        m_direction.x = -1;
        if( lastFace == EAST || !(action & (BUTTON_UP|BUTTON_DOWN)) ) {
            if ( !(action & BUTTON_STRAFE) )
                SetFace(WEST);
        } 
    } else if ( action & BUTTON_RIGHT ) {
        m_direction.x = 1;
        if( lastFace == WEST || !(action & (BUTTON_UP|BUTTON_DOWN)) ) {
            if ( !(action & BUTTON_STRAFE) )
                SetFace(EAST);
        }
    }

}


/** Set the direction */
void IEntity::SetFace( int f)
{ 
    m_oldFace   = m_face; 
    m_face      = f; 

    m_forward.ZeroOut();

    // make the correct forward vec
    switch( m_face )
    {
    case NORTH: m_forward.Set( 0, -1 );
        break;
    case SOUTH: m_forward.Set( 0, 1 );
        break;
    case EAST:  m_forward.Set( 1, 0 ); 
        break;
    case WEST:  m_forward.Set( -1, 0 );
        break;
    default:    m_forward.Set( 1, 1 );
        break;
    };
}